package com.fengye.security.custom.filter;

import com.alibaba.fastjson.JSON;
import com.fengye.common.constants.CommonConstant;
import com.fengye.common.utils.TokenExtractorUtils;
import com.fengye.security.custom.model.LoginUserDetails;
import com.fengye.common.utils.JwtUtil;
import com.fengye.common.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author sunlei
 * @date 2023/9/11 20:41:13
 * @description 自定义认证过滤器，【注意】在此处抛出的异常不会被自己自定义的全局异常处理器捕获，
 * 当请求通过 Spring Security 的过滤器链时，抛出的任何 AuthenticationException、AccessDeniedException 会被 ExceptionTranslationFilter 捕获，
 * 最终会交给 SpringSecurity 的 exceptionHandling() 异常处理器处理，AccessDeniedHandler（权限异常处理器）、AuthenticationEntryPoint（认证异常处理器）
 */
@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Autowired
    private RedisUtils redisUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取token
        String token = TokenExtractorUtils.getHeaderBearerToken(request);
        if (StringUtils.isNotEmpty(token)) {
            // 解析token
            String userId = validateAndParseToken(token);
            // 从redis获取用户信息
            LoginUserDetails loginUserDetails = getUserDetails(userId);
            if (loginUserDetails != null) {
                // 1、将获取到的【认证信息】和【权限信息】封装到Authentication中
                // 【注意】
                // 建议用三个参数的构造函数，因为三个构造函数生成的authentication表示已经成功认证（登录）过了，构造函数内部会调用这个 super.setAuthenticated(true);
                // 或者不用UsernamePasswordAuthenticationToken的三个参数的构造函数，但是需要手动调用 authenticationToken.setAuthenticated(true);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUserDetails, null, loginUserDetails.getAuthorities());
                // authenticationToken.setAuthenticated(true);
                // 2、存入SecurityContentHolder
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        // 放行，以便下一个过滤器处理
        filterChain.doFilter(request, response);
    }

    /**
     * 验证并解析token，返回解析出的userId或null（如果token无效或解析失败）
     * @param token 用户token
     * @return 解析出的userId或null
     */
    private String validateAndParseToken(String token) {
        try {
            // 这里可以添加对token的格式、过期时间等进行验证的逻辑
            String userId = JwtUtil.parseUserId(token);
            String oldToken = (String)redisUtils.get(CommonConstant.REDIS_LOGIN_TOKEN_KEY_PREFIX + userId);
            if (!StringUtils.equals(token, oldToken)) {
                log.error("token无效，已被重置==：{}", token);
                return null;
            }
            return userId;
        } catch (Exception e) {
            log.error("token无效，解析失败==：{}", token);
            return null;
        }
    }

    /**
     * 从redis获取用户信息，转换成UserDetails类型的数据
     * @param userId 用户id
     * @return UserDetails
     */
    private LoginUserDetails getUserDetails(String userId) {
        if (userId == null) {
            return null;
        }
        String redisKey = CommonConstant.REDIS_LOGIN_USER_DETAILS_KEY_PREFIX + userId;
        String userDetailsStr = (String)redisUtils.get(redisKey);
        if (StringUtils.isEmpty(userDetailsStr)) {
            return null;
        }
        return JSON.parseObject(userDetailsStr, LoginUserDetails.class);
    }
}
